/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Permissive License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Permissive License, please send an email to 
 * ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Permissive License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Threading;
using Microsoft.Scripting;
using Microsoft.Scripting.Internal;
using Microsoft.Scripting.Internal.Ast;
using Microsoft.Scripting.Internal.Generation;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Actions;
using Nua;
using Nua.Compiler;
using Nua.Compiler.Generation;
using Nua.Ast;
using Nua.Runtime;

namespace Nua.Hosting 
{

    using SingletonFactory = SingletonEngineFactory<NuaEngine, NuaEngineOptions, NuaLanguageProvider>;

    public sealed class NuaEngine : ScriptEngine, IDisposable {
        private static readonly object NuaLanguageContextKey = new object();
        private static readonly Guid NuaLanguageGuid = new Guid("03ed4b80-d10b-442f-ad9a-47dae85b2051");
        SystemState _systemState;
        
        private readonly NuaScriptCompiler _compiler;
        
        internal static readonly SingletonFactory Factory = new SingletonFactory(
            delegate(NuaLanguageProvider provider, NuaEngineOptions options) { return new NuaEngine(provider, options); },
            GetSetupInformation);

        // singleton:
        private readonly ErrorSink _defaultErrorSink;

        public override Guid LanguageGuid 
        {
            get 
            {
                return NuaLanguageGuid;
            }
        }

        public override string VersionString {
            get {
                return "Nua";
            }
        }
        
        public new NuaEngineOptions Options {
            get { return (NuaEngineOptions)base.Options; }
        }

        public NuaScriptCompiler ScriptCompiler {
            get { return _compiler; }
        }

        public override ScriptCompiler Compiler {
            get {
                return _compiler;
            }
        }

        public override ActionBinder DefaultBinder {
            
            get 
            {
                return Nua.Runtime.NuaBinder.Default;
            }
        }

        #region Construction

        /// <summary>
        /// Provides the current Python engine. Asks the current <see cref="ScriptEnvironment"/> for a one of it doesn't exist yet.
        /// </summary>
        public static NuaEngine CurrentEngine {
            get {
                return Factory.GetInstance();
            }
        }
        
        private static NuaEngineOptions GetSetupInformation() {
            // TODO: look-up appdomain data, config, etc. - maybe unified with the SE.GetSetupInfo
            return new NuaEngineOptions();
        }

        /// <summary>
        /// To be called by <see cref="GetLocalService"/> only.
        /// </summary>
        private NuaEngine(NuaLanguageProvider provider, NuaEngineOptions engineOptions)
            : base(provider, engineOptions) {

            // singletons:
            _defaultErrorSink = new ErrorSink();
 
            // TODO:
            //DefaultContext.CreateDefaultContexts(this); 
            _systemState = new SystemState(this);

            _compiler = new NuaScriptCompiler(this);
            
 
        }

        #endregion

        public override void SetSourceUnitSearchPaths(string[] paths) 
        {
            //throw new NotImplementedException();
        }

        public override IAttributesCollection GetGlobalsDictionary(IDictionary<string, object> globals) 
        {
            // TODO://
            return null;
        }

        public override void Shutdown() 
        {
            throw new NotImplementedException();
        }


        public void InitializeModules(string prefix, string executable, string version) 
        {
            throw new NotImplementedException();
        }

        public override TextWriter GetOutputWriter(bool isErrorOutput) 
        {
            return new Runtime.OutputWriter(isErrorOutput);
        }


        public ScriptModule CreateModule(string moduleName) 
        {
            return CreateModule(moduleName, new Dictionary<string, object>(), ModuleOptions.None);
        }

        public ScriptModule CreateModule(string moduleName, ModuleOptions options) 
        {
            return CreateModule(moduleName, new Dictionary<string, object>(), options);
        }

        /// <summary>
        /// Create a module. A module is required to be able to execute any code.
        /// </summary>
        /// <param name="moduleName"></param>
        /// <param name="globals"></param>
        /// <param name="publishModule">If this is true, the module will be published as sys.modules[moduleName].
        /// The module may later be unpublished by executing "del sys.modules[moduleName]". All resources associated
        /// with the module will be reclaimed after that.
        /// </param>
        public ScriptModule CreateModule(string moduleName, IDictionary<string, object> globals, ModuleOptions options) 
        {
            if (moduleName == null) throw new ArgumentNullException("moduleName");
            if (globals == null) throw new ArgumentNullException("globals");

            IAttributesCollection globalDict = globals as IAttributesCollection ?? GetGlobalsDictionary(globals);
            ScriptModule module = MakeNuaModule(moduleName, new Scope(globalDict), options);

            return module;
        }

        public ScriptModule CreateOptimizedModule(string fileName, string moduleName, bool publishModule) 
        {
            if (fileName == null) throw new ArgumentNullException("fileName");
            if (moduleName == null) throw new ArgumentNullException("moduleName");

            SourceFileUnit file_unit = new SourceFileUnit(this, fileName, moduleName, Encoding.ASCII);
            CompilerOptions options = GetDefaultCompilerOptions();
            ScriptModule module = file_unit.CompileToModule(options, GetDefaultErrorSink());

            return module;
        }


        public override void PublishModule(IScriptModule module) {
            if (module == null) throw new ArgumentNullException("module");

            // TODO: remote modules here...
            //_systemState.modules[module.ModuleName] = module;
        }

        public ScriptModule MakeNuaModule(string name, DynamicType type) 
        {
            if (type == null) throw new ArgumentNullException("type");

            // TODO: hack to enable __builtin__ reloading:
            //return MakePythonModule(name, new Scope(MakeModuleDictionary(type)), ModuleOptions.None);
            
            // import __builtin__
            // del __builtin__.pow
            // reload(__builtin__)
            // __builtin__.pow
            
            // creates an empty module:
            //ScriptModule module = ScriptDomainManager.CurrentManager.CompileModule(name, new Scope(MakeModuleDictionary(type)), null, null,
            //    new SourceCodeUnit(this, "", name));

            //SetModuleContext(module, new PythonModuleContext());

            //return module;

            throw new NotImplementedException();
        }

        public ScriptModule MakeNuaModule(string name) 
        {
            return MakeNuaModule(name, null, ModuleOptions.None);
        }

        public ScriptModule MakeNuaModule(string name, Scope scope) 
        {
            return MakeNuaModule(name, scope, ModuleOptions.None);
        }

        // scope can be null
        public ScriptModule MakeNuaModule(string name, Scope scope, ModuleOptions options) 
        {
            if (name == null) throw new ArgumentNullException("name");
            if (scope == null) scope = new Scope(new SymbolDictionary());

            ScriptModule module = ScriptDomainManager.CurrentManager.CreateModule(name, scope);
            
            if ((options & ModuleOptions.PublishModule) != 0) {
                PublishModule(module);
            }

            return module;

            throw new NotImplementedException();
        }

        private static IAttributesCollection MakeModuleDictionary(DynamicType type) 
        {
            //IAttributesCollection dict = new SymbolDictionary();

            //foreach (SymbolId attrName in type.GetTypeMembers(DefaultContext.Default)) {
            //    dict[attrName] = type.GetBoundMember(DefaultContext.Default, null, attrName);
            //}
            //return dict;
            throw new NotImplementedException();
        }

        public override int ExecuteProgram(SourceUnit sourceUnit) 
        {
            try {
                return base.ExecuteProgram(sourceUnit);
            } catch (Exception e) {
                object obj_code;
                return 0;
            }
        }
        
        public void Execute(string scriptCode, ScriptModule module, IDictionary<string, object> locals) 
        {
            Execute(new SourceCodeUnit(this, scriptCode), module, locals);
        }

        /// <summary>
        /// Execute the Python code.
        /// The API will throw any exceptions raised by the code. If PythonSystemExit is thrown, the host should 
        /// interpret that in a way that is appropriate for the host.
        /// </summary>
        /// <param name="sourceUnit">Source unit to execute.</param>
        /// <param name="scope">The scope to execute the code in.</param>
        /// <param name="locals">Dictionary of locals</param>
        public void Execute(SourceUnit sourceUnit, ScriptModule module, IDictionary<string, object> locals) 
        {
            if (sourceUnit == null) throw new ArgumentNullException("sourceUnit");
            if (module == null) throw new ArgumentNullException("module");

            ScriptCode code = ScriptCode.FromCompiledCode(sourceUnit.Compile(GetModuleCompilerOptions(module), _defaultErrorSink));
            code.Run(locals != null ? new Scope(module.Scope, GetAttrDict(locals)) : module.Scope);
        }

        public object Evaluate(string expression, ScriptModule module, IDictionary<string, object> locals) 
        {
            if (expression == null) throw new ArgumentNullException("expression");
            if (module == null) throw new ArgumentNullException("module");

            if (locals != null) {
                Scope newScope = new Scope(module.Scope, GetAttrDict(locals));
                module = MakeNuaModule(module.ModuleName, newScope);
            }

            return Evaluate(expression, module);
        }

        private static IAttributesCollection GetAttrDict(IDictionary<string, object> locals) 
        {
            //return locals as IAttributesCollection ?? new StringDictionaryAdapterDict(locals);
            throw new NotImplementedException();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T EvaluateAs<T>(string expression, ScriptModule module, IDictionary<string, object> locals) 
        {
            return ConvertObject<T>(Evaluate(expression, module, locals));
        }

        public override T ConvertObject<T>(object value) 
        {
            //return Converter.Convert<T>(value);
            throw new NotImplementedException();
        }

        #region Loader Members

        public void AddToPath(string directory) 
        {
           // _systemState.path.Append(directory);
        }

        public override void AddAssembly(Assembly assembly)
        {
            //Ops.TopPackage.LoadAssembly(assembly);
        }

        #endregion

        #region IDisposable Members

        public void Dispose() {
            Dispose(false);
        }

        private void Dispose(bool finalizing) {
            // if we're finalizing we shouldn't access other managed objects, as
            // their finalizers may have already run            
            if (!finalizing) {
                //_systemState.CloseStandardIOStreams();
            }
        }

        public void DumpDebugInfo()
        {

        }

        ~NuaEngine() {
            Dispose(true);
        }

        #endregion

        public override StreamReader GetSourceReader(Stream stream, ref Encoding encoding) {
            Debug.Assert(stream != null && encoding != null);
            Debug.Assert(stream.CanSeek && stream.CanRead);
            
            // we choose ASCII by default, if the file has a Unicode pheader though
            // we'll automatically get it as unicode.
            Encoding default_encoding = encoding;
            encoding = Utils.AsciiEncoding;

            long start_position = stream.Position;

            StreamReader sr = new StreamReader(stream, Utils.AsciiEncoding);
            string line = sr.ReadLine();
            bool gotEncoding = false;

            // re-read w/ the correct encoding type...
            stream.Seek(start_position, SeekOrigin.Begin);

            return new StreamReader(stream, encoding);
        }

        public override SourceUnit CreateStandardInputSourceUnit(string code) {
            return new SourceCodeUnit(this, code, "<stdin>");
        }

        // TODO (dependency workaround): 
        public override object GetSystemState() {
            //return _systemState;
            throw new NotImplementedException();
        }

        #region Factories

        public override ErrorSink GetDefaultErrorSink() 
        {
            return _defaultErrorSink;
        }

        public override CompilerOptions GetDefaultCompilerOptions() 
        {
            return new NuaCompilerOptions();
        }

        public override CompilerOptions GetModuleCompilerOptions(ScriptModule module) 
        {
            throw new NotImplementedException();
        }

        protected override LanguageContext GetLanguageContext(ScriptModule module) 
        {
            throw new NotImplementedException();
        }

        protected override LanguageContext GetLanguageContext(CompilerOptions compilerOptions) 
        {
            return new Nua.Runtime.NuaContext(this);
        }


        // TODO: should return PythonContext
        public override object TryGetModuleContext(ScriptModule module) {
           
            throw new NotImplementedException();
        }

        // TODO: internal and take PythonModuleContext
        public override void SetModuleContext(ScriptModule module, object context) 
        {

        }

        #endregion

        #region Runtime Code Sense

        protected override string[] FormatObjectMemberNames(IList<object> names) 
        {
            throw new NotImplementedException();
        }

        public override string[] GetObjectCallSignatures(object obj) 
        {
            throw new NotImplementedException();
        }

        public override string GetObjectDocumentation(object obj) 
        {
            throw new NotImplementedException();
        }

        #endregion

        #region // TODO: workarounds

        protected override IList<object> Ops_GetAttrNames(CodeContext context, object obj) 
        {
            throw new NotImplementedException();
        }

        protected override bool Ops_TryGetAttr(CodeContext context, object obj, SymbolId id, out object value) 
        {
            throw new NotImplementedException();
        }

        protected override bool Ops_IsCallable(CodeContext context, object obj) 
        {
            throw new NotImplementedException();
        }

        protected override object Ops_Call(CodeContext context, object obj, object[] args)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
